﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Data.Linq;
using System.Linq.Dynamic;
using Htse.Blue.Models;
using System.ComponentModel;
using System.Reflection;
using System.Data.Linq.Mapping;
using System.Linq.Expressions;
using Htse.Blue.Helpers;

namespace Htse.Blue.Data
{
	public abstract class LinqToSqlRepositoryBase<T, K, DC> : IRepository<T, K>
		where T : class, new()
		where K : struct
		where DC : DataContext, new()
	{
		protected DC dataContext = LinqToSqlDataContextFactory.GetWebRequestScopedDataContext<DC>();

		#region IRepository<T> Members

		public virtual IQueryable<T> GetAll()
		{
			return dataContext.GetTable<T>().AsQueryable();
		}

		public virtual IQueryable<T> GetAll(Expression<Func<T, bool>> expression)
		{
			return dataContext.GetTable<T>().Where(expression).AsQueryable();
		}

		public virtual T GetById(K id)
		{
			var identityProperty = GetEntityIdentity();

			var expression = GetEqualExpressionFromProperty<T>(
				identityProperty.Name, id);

			return dataContext.GetTable<T>().SingleOrDefault<T>(expression);
		}

		public virtual T GetSingle(Expression<Func<T, bool>> expression)
		{
			return dataContext.GetTable<T>().SingleOrDefault(expression);
		}

		public virtual T Create(T entityToCreate)
		{
			dataContext.GetTable<T>().InsertOnSubmit(entityToCreate);

			dataContext.SubmitChanges();

			return entityToCreate;
		}

		public virtual T SaveOrUpdate(T entityToSave)
		{
			T originalEntity = GetOriginalEntity(entityToSave);

			try
			{
				dataContext.GetTable<T>().Attach(entityToSave, originalEntity);
			}
			catch (InvalidOperationException)
			{
			}

			dataContext.SubmitChanges();

			return entityToSave;
		}

		public virtual void Delete(T entityToDelete)
		{
			dataContext.GetTable<T>().DeleteOnSubmit(entityToDelete);

			dataContext.SubmitChanges();
		}

		#endregion

		private T GetOriginalEntity(T entity)
		{
			T originalEntity = new T();

			string identityMemberName = GetEntityIdentity().Name;

			PropertyInfo memberNamePropertyInfo = typeof(T).GetProperty(identityMemberName);

			memberNamePropertyInfo.SetValue(
					originalEntity,
					memberNamePropertyInfo.GetValue(entity, null),
					null);

			string versionMemberName = dataContext.Mapping.GetMetaType(typeof(T)).VersionMember.Name;

			PropertyInfo versionPropertyInfo = typeof(T).GetProperty(versionMemberName);
			versionPropertyInfo.SetValue(
				originalEntity,
				versionPropertyInfo.GetValue(entity, null),
				null);

			return originalEntity;
		}

		private PropertyInfo GetEntityIdentity()
		{
			MetaDataMember identityMember =
						dataContext.Mapping.GetMetaType(typeof(T)).IdentityMembers.SingleOrDefault();

			PropertyInfo pi = typeof(T).GetProperty(identityMember.Name);

			return pi;
		}

		private Expression<Func<TT, bool>> GetEqualExpressionFromProperty<TT>
			(string propertyName, object propertyValue)
		{
			var itemExpression = Expression.Parameter(typeof(TT), "t");

			Expression<Func<TT, bool>> expression = Expression.Lambda<Func<TT, bool>>(
				Expression.Equal(Expression.Property(itemExpression, propertyName),
				Expression.Constant(propertyValue)), itemExpression);

			return expression;
		}
	}
}
